package com.jappyframework;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.jappyframework.api.WebService;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.org.apache.xerces.internal.parsers.DOMParser;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

/**
 * This class Modifies the WEB-INF/web.xml file with the appropriate servlet
 * specifications.
 * 
 * @author harsh
 */
public class WebInfModifier {

	/**
	 * Template File.
	 */
	private final File templateFile;

	/**
	 * Output File.
	 */
	private final File outputFile;

	/**
	 * Parsed xml created from the xml content in xmlFile.
	 */
	private Document xmlDocument;

	/**
	 * Constructor to parse the xmlFile into the xmlNode.
	 * 
	 * @param xmlFile
	 * @throws JappyException
	 */
	public WebInfModifier(String templateFile, String outputFile)
			throws JappyException {
		try {
			this.templateFile = new File(templateFile);
			this.outputFile = new File(outputFile);
			if (!this.templateFile.canRead()) {
				throw new JappyException("Can not read : " + templateFile);
			}

			// This is really bad as we can not regenerate the entire web.xml
			// file at once,
			// because annotation processor will not be called with all
			// annotations always.
			DOMParser parser = new DOMParser();
			if (this.outputFile.exists()) {
				try {
					parser.parse(this.outputFile.toURI().toString());
				} catch (Exception e) {
					this.outputFile.delete();
					this.outputFile.createNewFile();
					parser.parse(this.templateFile.toURI().toString());
				}
			} else {
				this.outputFile.createNewFile();
				parser.parse(this.templateFile.toURI().toString());
			}
			this.xmlDocument = parser.getDocument();
			mDirty = false;

		} catch (Exception e) {
			throw new JappyException(e);
		}
	}

	private String getChildNodeWithName(Node node, String name) {
		NodeList children = node.getChildNodes();
		for (int i = 0; i < children.getLength(); ++i) {
			Node child = children.item(i);
			if (child.getNodeName().equals(name)) {
				return child.getTextContent();
			}
		}
		return "";
	}

	/**
	 * @param servletClassName
	 * @return
	 */
	private Node getServletNode(String servletClassName) {
		NodeList list = xmlDocument.getElementsByTagName("servlet");
		for (int i = 0; i < list.getLength(); ++i) {
			Node node = list.item(i);
			if (getChildNodeWithName(node, "servlet-class").equals(
					servletClassName)) {
				return node;
			}
		}
		return null;
	}

	/**
	 * 
	 * @param servletName
	 * @return
	 */
	private Node getServletMappingNode(String servletName) {
		NodeList list = xmlDocument.getElementsByTagName("servlet-mapping");
		for (int i = 0; i < list.getLength(); ++i) {
			Node node = list.item(i);
			if (getChildNodeWithName(node, "servlet-name").equals(servletName)) {
				return node;
			}
		}
		return null;
	}

	/**
	 * Remove the recipe.
	 * 
	 * @param classDecl
	 */
	private void removeRecipe(ClassDeclaration classDecl) {
		WebService recipe = classDecl.getAnnotation(WebService.class);
		if (recipe.pathname() == null) {
			return;
		}
		String servletName = classDecl.getSimpleName() + "Servlet";
		if (servletName.length() == 0) {
			servletName = classDecl.getSimpleName() + "Servlet";
		}
		Node servlet = getServletNode(classDecl.getPackage().getQualifiedName()
				+ "." + servletName);
		if (servlet != null) {
			servlet.getParentNode().removeChild(servlet);
		}
		Node mapping = getServletMappingNode(servletName);
		if (mapping != null) {
			mapping.getParentNode().removeChild(mapping);
		}
	}

	private boolean isDirty(ClassDeclaration classDecl) {
		String servletName = classDecl.getSimpleName() + "Servlet";
		Node servlet = getServletNode(classDecl.getPackage().getQualifiedName()
				+ "." + servletName);
		if (servlet == null)
			return true;
		Node mapping = getServletMappingNode(servletName);
		if (mapping == null)
			return true;
		return false;
	}

	/**
	 * Add the given recipe to the xml file. If it exists its modified.
	 * 
	 * @param recipe
	 */
	public void addRecipe(ClassDeclaration classDecl) {
		WebService recipe = classDecl.getAnnotation(WebService.class);
		if (recipe.pathname() == null) {
			return;
		}
		if (!isDirty(classDecl))
			return;
		mDirty = true;
		removeRecipe(classDecl);
		String servletClassName = classDecl.getSimpleName() + "Servlet";
		{
			Node servlet = xmlDocument.createElement("servlet");
			xmlDocument.getFirstChild().appendChild(servlet);

			Node servletName = xmlDocument.createElement("servlet-name");
			servletName.appendChild(xmlDocument
					.createTextNode(servletClassName));
			servlet.appendChild(servletName);

			Node servletClass = xmlDocument.createElement("servlet-class");
			servletClass.appendChild(xmlDocument.createTextNode(classDecl
					.getPackage().getQualifiedName()
					+ "." + servletClassName));
			servlet.appendChild(servletClass);
		}
		{
			Node servletMapping = xmlDocument.createElement("servlet-mapping");
			xmlDocument.getFirstChild().appendChild(servletMapping);

			Node servletName = xmlDocument.createElement("servlet-name");
			servletName.appendChild(xmlDocument
					.createTextNode(servletClassName));
			servletMapping.appendChild(servletName);

			Node urlPattern = xmlDocument.createElement("url-pattern");
			urlPattern.appendChild(xmlDocument.createTextNode("/"
					+ recipe.pathname()));
			servletMapping.appendChild(urlPattern);
		}
	}

	/**
	 * @throws JappyException
	 */
	public void commit() throws JappyException {
		if (!mDirty)
			return;
		try {
			FileOutputStream fout = new FileOutputStream(this.outputFile);
			OutputFormat format = new OutputFormat();
			format.setIndent(2);
			XMLSerializer serializer = new XMLSerializer(fout, format);
			serializer.serialize(this.xmlDocument);
			fout.flush();
			fout.close();
		} catch (FileNotFoundException e) {
			throw new JappyException(e.getMessage());
		} catch (IOException e) {
			throw new JappyException(e.getMessage());
		}
	}

	private boolean mDirty;

}
